<!DOCTYPE html>



  


<html class="theme-next pisces use-motion" lang="zh-Hans">
<head>
  <meta charset="UTF-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"/>
<meta name="theme-color" content="#222">
<meta name="referrer" content="same-origin">
<meta name="referrer" content="no-referrer" />








<meta http-equiv="Cache-Control" content="no-transform" />
<meta http-equiv="Cache-Control" content="no-siteapp" />
















  
  
  <link href="/lib/fancybox/source/jquery.fancybox.css?v=2.1.5" rel="stylesheet" type="text/css" />







<link href="/lib/font-awesome/css/font-awesome.min.css?v=4.6.2" rel="stylesheet" type="text/css" />

<link href="/css/main.css?v=5.1.4" rel="stylesheet" type="text/css" />


  <link rel="apple-touch-icon" sizes="180x180" href="/images/apple-touch-icon-next.png?v=5.1.4">


  <link rel="icon" type="image/png" sizes="32x32" href="/images/favicon-32x32-next.png?v=5.1.4">


  <link rel="icon" type="image/png" sizes="16x16" href="/images/favicon-16x16-next.png?v=5.1.4">


  <link rel="mask-icon" href="/images/logo.svg?v=5.1.4" color="#222">





  <meta name="keywords" content="mybatis," />










<meta name="description" content="&amp;emsp;&amp;emsp;接上面从mybatis到mybatis-spring-boot-starter，主要是通过解决上文留下的几个问题，探讨在mybatis-spring-boot-starter中MyBatis如何与Spring（Spring Boot）进行互动。上文中写了一个Demo使用MyBatis原生的方式来测试从数据库中获取数据，本文稍微修改了一下，使用Spring Boot集成的方式">
<meta name="keywords" content="mybatis">
<meta property="og:type" content="article">
<meta property="og:title" content="从mybatis到mybatis-spring-boot-starter（二）">
<meta property="og:url" content="http://yoursite.com/2020/12/27/从mybatis到mybatis-spring-boot-starter（二）/index.html">
<meta property="og:site_name" content="矩阵编程">
<meta property="og:description" content="&amp;emsp;&amp;emsp;接上面从mybatis到mybatis-spring-boot-starter，主要是通过解决上文留下的几个问题，探讨在mybatis-spring-boot-starter中MyBatis如何与Spring（Spring Boot）进行互动。上文中写了一个Demo使用MyBatis原生的方式来测试从数据库中获取数据，本文稍微修改了一下，使用Spring Boot集成的方式">
<meta property="og:locale" content="zh-Hans">
<meta property="og:updated_time" content="2020-12-27T11:48:34.064Z">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="从mybatis到mybatis-spring-boot-starter（二）">
<meta name="twitter:description" content="&amp;emsp;&amp;emsp;接上面从mybatis到mybatis-spring-boot-starter，主要是通过解决上文留下的几个问题，探讨在mybatis-spring-boot-starter中MyBatis如何与Spring（Spring Boot）进行互动。上文中写了一个Demo使用MyBatis原生的方式来测试从数据库中获取数据，本文稍微修改了一下，使用Spring Boot集成的方式">



<script type="text/javascript" id="hexo.configurations">
  var NexT = window.NexT || {};
  var CONFIG = {
    root: '/',
    scheme: 'Pisces',
    version: '5.1.4',
    sidebar: {"position":"left","display":"post","offset":12,"b2t":false,"scrollpercent":false,"onmobile":false},
    fancybox: true,
    tabs: true,
    motion: {"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"slideDownIn","post_body":"slideDownIn","coll_header":"slideLeftIn","sidebar":"slideUpIn"}},
    duoshuo: {
      userId: '0',
      author: '博主'
    },
    algolia: {
      applicationID: '',
      apiKey: '',
      indexName: '',
      hits: {"per_page":10},
      labels: {"input_placeholder":"Search for Posts","hits_empty":"We didn't find any results for the search: ${query}","hits_stats":"${hits} results found in ${time} ms"}
    }
  };
</script>



  <link rel="canonical" href="http://yoursite.com/2020/12/27/从mybatis到mybatis-spring-boot-starter（二）/"/>





  <title>从mybatis到mybatis-spring-boot-starter（二） | 矩阵编程</title>
  





  <script type="text/javascript">
    var _hmt = _hmt || [];
    (function() {
      var hm = document.createElement("script");
      hm.src = "https://hm.baidu.com/hm.js?7d29e6fba8e07d1a8af2414a20e5f2a6";
      var s = document.getElementsByTagName("script")[0];
      s.parentNode.insertBefore(hm, s);
    })();
  </script>




</head>

<body itemscope itemtype="http://schema.org/WebPage" lang="zh-Hans">

  
  
    
  

  <div class="container sidebar-position-left page-post-detail">
    <div class="headband"></div>

    <header id="header" class="header" itemscope itemtype="http://schema.org/WPHeader">
      <div class="header-inner"><div class="site-brand-wrapper">
  <div class="site-meta ">
    

    <div class="custom-logo-site-title">
      <a href="/"  class="brand" rel="start">
        <span class="logo-line-before"><i></i></span>
        <span class="site-title">矩阵编程</span>
        <span class="logo-line-after"><i></i></span>
      </a>
    </div>
      
        <p class="site-subtitle"></p>
      
  </div>

  <div class="site-nav-toggle">
    <button>
      <span class="btn-bar"></span>
      <span class="btn-bar"></span>
      <span class="btn-bar"></span>
    </button>
  </div>
</div>

<nav class="site-nav">
  

  
    <ul id="menu" class="menu">
      
        
        <li class="menu-item menu-item-主页">
          <a href="/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-主页"></i> <br />
            
            主页
          </a>
        </li>
      
        
        <li class="menu-item menu-item-categories">
          <a href="/categories/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-分类"></i> <br />
            
            分类
          </a>
        </li>
      
        
        <li class="menu-item menu-item-所有文章">
          <a href="/archives/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-文章"></i> <br />
            
            所有文章
          </a>
        </li>
      

      
    </ul>
  

  
</nav>



 </div>
    </header>

    <main id="main" class="main">
      <div class="main-inner">
        <div class="content-wrap">
          <div id="content" class="content">
            

  <div id="posts" class="posts-expand">
    

  

  
  
  

  <article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
  
  
  
  <div class="post-block">
    <link itemprop="mainEntityOfPage" href="http://yoursite.com/2020/12/27/从mybatis到mybatis-spring-boot-starter（二）/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="matrix">
      <meta itemprop="description" content="">
      <meta itemprop="image" content="/images/avatar.gif">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="矩阵编程">
    </span>

    
      <header class="post-header">

        
        
          <h1 class="post-title" itemprop="name headline">从mybatis到mybatis-spring-boot-starter（二）</h1>
        

        <div class="post-meta">
          <span class="post-time">
            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
                <span class="post-meta-item-text">发表于</span>
              
              <time title="创建于" itemprop="dateCreated datePublished" datetime="2020-12-27T19:48:34+08:00">
                2020-12-27
              </time>
            

            

            
          </span>

          
            <span class="post-category" >
            
              <span class="post-meta-divider">|</span>
            
              <span class="post-meta-item-icon">
                <i class="fa fa-folder-o"></i>
              </span>
              
                <span class="post-meta-item-text">分类于</span>
              
              
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/mybatis/" itemprop="url" rel="index">
                    <span itemprop="name">mybatis</span>
                  </a>
                </span>

                
                
              
            </span>
          

          
            
              <span class="post-comments-count">
                <span class="post-meta-divider">|</span>
                <span class="post-meta-item-icon">
                  <i class="fa fa-comment-o"></i>
                </span>
                <a href="/2020/12/27/从mybatis到mybatis-spring-boot-starter（二）/#comments" itemprop="discussionUrl">
                  <span class="post-comments-count disqus-comment-count"
                        data-disqus-identifier="2020/12/27/从mybatis到mybatis-spring-boot-starter（二）/" itemprop="commentCount"></span>
                </a>
              </span>
            
          

          
          

          

          

          

        </div>
      </header>
    

    
    
    
    <div class="post-body" itemprop="articleBody">

      
      

      
        <p>&emsp;&emsp;接上面<a href="/2020/12/22/从mybatis到mybatis-spring-boot-starter">从mybatis到mybatis-spring-boot-starter</a>，主要是通过解决上文留下的几个问题，探讨在mybatis-spring-boot-starter中MyBatis如何与Spring（Spring Boot）进行互动。上文中写了一个Demo使用MyBatis原生的方式来测试从数据库中获取数据，本文稍微修改了一下，使用Spring Boot集成的方式来获取数据：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@CacheNamespace</span></span><br><span class="line"><span class="meta">@Mapper</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">BlogMapper</span> </span>&#123;</span><br><span class="line">    <span class="meta">@Select</span>(<span class="string">"SELECT * FROM blog WHERE id = #&#123;id&#125;"</span>)</span><br><span class="line">    <span class="function">Blog <span class="title">selectBlog</span><span class="params">(<span class="keyword">int</span> id)</span></span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">BlogService</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> BlogMapper mapper;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">BlogService</span><span class="params">(BlogMapper mapper)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">this</span>.mapper = mapper;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@PostConstruct</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">init</span><span class="params">()</span></span>&#123;</span><br><span class="line">        Blog b = mapper.selectBlog(<span class="number">1</span>);</span><br><span class="line">        System.out.println(b.getArticle());</span><br><span class="line">        System.out.println(b.getContent());</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>还有一些数据库的参数配置spring.datasource.xxx等忽略，就从这个例子开始。<br><a id="more"></a></p>
<h3 id="Mapper注入"><a href="#Mapper注入" class="headerlink" title="Mapper注入"></a>Mapper注入</h3><p>&emsp;&emsp;首先如果要注入Spring Bean，先必须保证该Bean会被Spring扫描到，这里可以看到<strong>BlogMapper</strong>上面比前文多了一个注解<strong>@Mapper</strong>，这个Mapper注解（当然还有一些其他常用注解如MapperScan）就是Spring能扫描到关键。对<strong>Spring Boot Starter</strong>有了解的人应该知道，分析一个xxx-spring-boot-starter项目的时候，应该先看他的<strong>spring.factories</strong>文件，这是整个项目扫描、自动装配的入口：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">org.springframework.boot.autoconfigure.EnableAutoConfiguration=\</span><br><span class="line">org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration,\</span><br><span class="line">org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration</span><br></pre></td></tr></table></figure></p>
<p>这里有两个入口，MybatisLanguageDriverAutoConfiguration像是xml相关的模板解析，重点关注<strong>MybatisAutoConfiguration</strong>，下面贴几个<strong>MybatisAutoConfiguration</strong>的方法，会省略部分代码：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Bean</span></span><br><span class="line"><span class="meta">@ConditionalOnMissingBean</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> SqlSessionFactory <span class="title">sqlSessionFactory</span><span class="params">(DataSource dataSource)</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">    <span class="comment">// ...省略里面的代码...</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Bean</span></span><br><span class="line"><span class="meta">@ConditionalOnMissingBean</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> SqlSessionTemplate <span class="title">sqlSessionTemplate</span><span class="params">(SqlSessionFactory sqlSessionFactory)</span> </span>&#123;</span><br><span class="line">    <span class="comment">// ...省略里面的代码...</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">AutoConfiguredMapperScannerRegistrar</span> <span class="keyword">implements</span> <span class="title">BeanFactoryAware</span>, <span class="title">ImportBeanDefinitionRegistrar</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">private</span> BeanFactory beanFactory;</span><br><span class="line"></span><br><span class="line">  <span class="meta">@Override</span></span><br><span class="line">  <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">registerBeanDefinitions</span><span class="params">(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry)</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (!AutoConfigurationPackages.has(<span class="keyword">this</span>.beanFactory)) &#123;</span><br><span class="line">      logger.debug(<span class="string">"Could not determine auto-configuration package, automatic mapper scanning disabled."</span>);</span><br><span class="line">      <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    logger.debug(<span class="string">"Searching for mappers annotated with @Mapper"</span>);</span><br><span class="line"></span><br><span class="line">    List&lt;String&gt; packages = AutoConfigurationPackages.get(<span class="keyword">this</span>.beanFactory);</span><br><span class="line">    <span class="keyword">if</span> (logger.isDebugEnabled()) &#123;</span><br><span class="line">      packages.forEach(pkg -&gt; logger.debug(<span class="string">"Using auto-configuration base package '&#123;&#125;'"</span>, pkg));</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);</span><br><span class="line">    builder.addPropertyValue(<span class="string">"processPropertyPlaceHolders"</span>, <span class="keyword">true</span>);</span><br><span class="line">    builder.addPropertyValue(<span class="string">"annotationClass"</span>, Mapper.class);</span><br><span class="line">    builder.addPropertyValue(<span class="string">"basePackage"</span>, StringUtils.collectionToCommaDelimitedString(packages));</span><br><span class="line">    BeanWrapper beanWrapper = <span class="keyword">new</span> BeanWrapperImpl(MapperScannerConfigurer.class);</span><br><span class="line">    Stream.of(beanWrapper.getPropertyDescriptors())</span><br><span class="line">        <span class="comment">// Need to mybatis-spring 2.0.2+</span></span><br><span class="line">        .filter(x -&gt; x.getName().equals(<span class="string">"lazyInitialization"</span>)).findAny()</span><br><span class="line">        .ifPresent(x -&gt; builder.addPropertyValue(<span class="string">"lazyInitialization"</span>, <span class="string">"$&#123;mybatis.lazy-initialization:false&#125;"</span>));</span><br><span class="line">    registry.registerBeanDefinition(MapperScannerConfigurer.class.getName(), builder.getBeanDefinition());</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="meta">@Override</span></span><br><span class="line">  <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setBeanFactory</span><span class="params">(BeanFactory beanFactory)</span> </span>&#123;</span><br><span class="line">    <span class="keyword">this</span>.beanFactory = beanFactory;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<ol>
<li>使用配置的<strong>DataSource</strong>实例化<strong>SqlSessionFactory</strong></li>
<li>实例化<strong>SqlSessionTemplate</strong>，注意<strong>SqlSessionTemplate</strong>继承于SqlSession</li>
<li><strong>AutoConfiguredMapperScannerRegistrar</strong>这个类的加载是有条件的，比如使用@MapperScan或者@MapperScans注解扫描，<strong>AutoConfiguredMapperScannerRegistrar</strong>将不会执行，否则执行<strong>AutoConfiguredMapperScannerRegistrar#registerBeanDefinitions</strong>逻辑。</li>
<li>这里注册了<strong>MapperScannerConfigurer</strong>到Spring中，并且初始化一些<strong>MapperScannerConfigurer</strong>的参数，比如basePackage、annotationClass，这两个参数影响了后续扫描的动作。</li>
</ol>
<p><strong>ImportBeanDefinitionRegistrar</strong>在<a href="/2020/11/29/记一次nacos刷新失败排查（二）">记一次nacos刷新失败排查（二）</a>中也介绍过，实现该接口的类拥有注册Bean的能力。这里注册了<strong>MapperScannerConfigurer</strong>，<strong>MapperScannerConfigurer</strong>继承于<strong>BeanDefinitionRegistryPostProcessor</strong>，<strong>BeanDefinitionRegistryPostProcessor</strong>是Spring的一个后置处理器，继承于它可以获取Bean的相关定义，直接看实现方法<strong>MapperScannerConfigurer#postProcessBeanDefinitionRegistry</strong>：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">postProcessBeanDefinitionRegistry</span><span class="params">(BeanDefinitionRegistry registry)</span> </span>&#123;</span><br><span class="line">  <span class="keyword">if</span> (<span class="keyword">this</span>.processPropertyPlaceHolders) &#123;</span><br><span class="line">    processPropertyPlaceHolders();</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  ClassPathMapperScanner scanner = <span class="keyword">new</span> ClassPathMapperScanner(registry);</span><br><span class="line">  scanner.setAddToConfig(<span class="keyword">this</span>.addToConfig);</span><br><span class="line">  scanner.setAnnotationClass(<span class="keyword">this</span>.annotationClass);</span><br><span class="line">  scanner.setMarkerInterface(<span class="keyword">this</span>.markerInterface);</span><br><span class="line">  scanner.setSqlSessionFactory(<span class="keyword">this</span>.sqlSessionFactory);</span><br><span class="line">  scanner.setSqlSessionTemplate(<span class="keyword">this</span>.sqlSessionTemplate);</span><br><span class="line">  scanner.setSqlSessionFactoryBeanName(<span class="keyword">this</span>.sqlSessionFactoryBeanName);</span><br><span class="line">  scanner.setSqlSessionTemplateBeanName(<span class="keyword">this</span>.sqlSessionTemplateBeanName);</span><br><span class="line">  scanner.setResourceLoader(<span class="keyword">this</span>.applicationContext);</span><br><span class="line">  scanner.setBeanNameGenerator(<span class="keyword">this</span>.nameGenerator);</span><br><span class="line">  scanner.setMapperFactoryBeanClass(<span class="keyword">this</span>.mapperFactoryBeanClass);</span><br><span class="line">  <span class="keyword">if</span> (StringUtils.hasText(lazyInitialization)) &#123;</span><br><span class="line">    scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));</span><br><span class="line">  &#125;</span><br><span class="line">  scanner.registerFilters();</span><br><span class="line">  scanner.scan(</span><br><span class="line">      StringUtils.tokenizeToStringArray(<span class="keyword">this</span>.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<ol>
<li><strong>processPropertyPlaceHolders</strong>初始化一些带占位符的property。</li>
<li>实例化<strong>ClassPathMapperScanner</strong>，<strong>ClassPathMapperScanner</strong>继承于<strong>ClassPathBeanDefinitionScanner</strong>，<strong>ClassPathBeanDefinitionScanner</strong>在<a href="/2019/04/03/谈谈Spring的IoC之注解扫描">谈谈Spring的IoC之注解扫描</a>中出现过，相当于Spring Bean扫描器。</li>
<li><strong>ClassPathMapperScanner#setAnnotationClass</strong>将<strong>Mapper.class</strong>设置进去，然后调用<strong>ClassPathMapperScanner#registerFilters</strong>扫描指定basePackages下带<strong>Mapper</strong>注解的类，即上面示例中的<strong>BlogMapper</strong>。</li>
</ol>
<p>以上是扫描<strong>Mapper</strong>的逻辑，那么如何实例化<strong>Mapper</strong>？继续看<strong>ClassPathMapperScanner#doScan</strong>扫描逻辑：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> Set&lt;BeanDefinitionHolder&gt; <span class="title">doScan</span><span class="params">(String... basePackages)</span> </span>&#123;</span><br><span class="line">  Set&lt;BeanDefinitionHolder&gt; beanDefinitions = <span class="keyword">super</span>.doScan(basePackages);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (beanDefinitions.isEmpty()) &#123;</span><br><span class="line">    LOGGER.warn(() -&gt; <span class="string">"No MyBatis mapper was found in '"</span> + Arrays.toString(basePackages)</span><br><span class="line">        + <span class="string">"' package. Please check your configuration."</span>);</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    processBeanDefinitions(beanDefinitions);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> beanDefinitions;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>扫描到<strong>Mapper</strong>后使用<strong>ClassPathMapperScanner#processBeanDefinitions</strong>处理：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">processBeanDefinitions</span><span class="params">(Set&lt;BeanDefinitionHolder&gt; beanDefinitions)</span> </span>&#123;</span><br><span class="line">  GenericBeanDefinition definition;</span><br><span class="line">  <span class="keyword">for</span> (BeanDefinitionHolder holder : beanDefinitions) &#123;</span><br><span class="line">    definition = (GenericBeanDefinition) holder.getBeanDefinition();</span><br><span class="line">    String beanClassName = definition.getBeanClassName();</span><br><span class="line">    LOGGER.debug(() -&gt; <span class="string">"Creating MapperFactoryBean with name '"</span> + holder.getBeanName() + <span class="string">"' and '"</span> + beanClassName</span><br><span class="line">        + <span class="string">"' mapperInterface"</span>);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// the mapper interface is the original class of the bean</span></span><br><span class="line">    <span class="comment">// but, the actual class of the bean is MapperFactoryBean</span></span><br><span class="line">    definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); <span class="comment">// issue #59</span></span><br><span class="line">    definition.setBeanClass(<span class="keyword">this</span>.mapperFactoryBeanClass);</span><br><span class="line"></span><br><span class="line">    definition.getPropertyValues().add(<span class="string">"addToConfig"</span>, <span class="keyword">this</span>.addToConfig);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// ...删除了下面一部分代码...</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>重新设置了<strong>MapperFactoryBean</strong>作为<strong>beanClass</strong>。<strong>MapperFactoryBean</strong>继承于<strong>FactoryBean</strong>，<strong>FactoryBean</strong>是一个工厂Bean，使用它可以自定义整个Bean的创建（拥有动态创建Bean的能力）。看一下<strong>MapperFactoryBean#getObject</strong>如何获取Bean：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> T <span class="title">getObject</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>&#123;</span><br><span class="line">  <span class="keyword">return</span> getSqlSession().getMapper(<span class="keyword">this</span>.mapperInterface);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>这里getSqlSession()返回<strong>SqlSessionTemplate</strong>，mapperInterface为Mapper注解的真实Class，即调用<strong>SqlSessionTemplate</strong>获取Mapper，上面说过<strong>SqlSessionTemplate</strong>继承于<strong>SqlSession</strong>，这个获取Mapper的动作不就是和<a href="/2020/12/22/从mybatis到mybatis-spring-boot-starter">从mybatis到mybatis-spring-boot-starter</a>示例中手动获取BlogMapper一样的么？以上通过<strong>MapperFactoryBean</strong>获取了Mapper注解的实例，但是还差一步，<strong>Configuration</strong>是如何感知这个Mapper？还是得从<strong>MapperFactoryBean</strong>上说起，<strong>MapperFactoryBean</strong>的继承关系如下：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">InitializingBean</span><br><span class="line">        |</span><br><span class="line">   DaoSupport</span><br><span class="line">        |</span><br><span class="line">SqlSessionDaoSupport   FactoryBean</span><br><span class="line">             |             |</span><br><span class="line">            MapperFactoryBean</span><br></pre></td></tr></table></figure></p>
<p><strong>DaoSupport</strong>提供一个模板方法，在<strong>InitializingBean#afterPropertiesSet</strong>后调用<strong>DaoSupport#checkDaoConfig</strong>，<strong>MapperFactoryBean#checkDaoConfig</strong>覆写逻辑如下：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">checkDaoConfig</span><span class="params">()</span> </span>&#123;</span><br><span class="line">  <span class="keyword">super</span>.checkDaoConfig();</span><br><span class="line"></span><br><span class="line">  notNull(<span class="keyword">this</span>.mapperInterface, <span class="string">"Property 'mapperInterface' is required"</span>);</span><br><span class="line"></span><br><span class="line">  Configuration configuration = getSqlSession().getConfiguration();</span><br><span class="line">  <span class="keyword">if</span> (<span class="keyword">this</span>.addToConfig &amp;&amp; !configuration.hasMapper(<span class="keyword">this</span>.mapperInterface)) &#123;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">      configuration.addMapper(<span class="keyword">this</span>.mapperInterface);</span><br><span class="line">    &#125; <span class="keyword">catch</span> (Exception e) &#123;</span><br><span class="line">      logger.error(<span class="string">"Error while adding the mapper '"</span> + <span class="keyword">this</span>.mapperInterface + <span class="string">"' to configuration."</span>, e);</span><br><span class="line">      <span class="keyword">throw</span> <span class="keyword">new</span> IllegalArgumentException(e);</span><br><span class="line">    &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">      ErrorContext.instance().reset();</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>将mapperInterface添加到<strong>Configuration</strong>中就是这么简单。</p>
<h3 id="SqlSession管理"><a href="#SqlSession管理" class="headerlink" title="SqlSession管理"></a>SqlSession管理</h3><p>&emsp;&emsp;从上面可以看出对于<strong>SqlSession</strong>的管理，其实是来自于<strong>SqlSessionTemplate</strong>对于<strong>SqlSession</strong>的管理，<strong>SqlSessionTemplate</strong>其实是一个代理，内部又维护了一个<strong>SqlSession</strong>，所有增删改查其实都是由内部的<strong>SqlSession</strong>执行，看一下<strong>SqlSessionTemplate</strong>的构造函数：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="title">SqlSessionTemplate</span><span class="params">(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,</span></span></span><br><span class="line"><span class="function"><span class="params">    PersistenceExceptionTranslator exceptionTranslator)</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">  notNull(sqlSessionFactory, <span class="string">"Property 'sqlSessionFactory' is required"</span>);</span><br><span class="line">  notNull(executorType, <span class="string">"Property 'executorType' is required"</span>);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">this</span>.sqlSessionFactory = sqlSessionFactory;</span><br><span class="line">  <span class="keyword">this</span>.executorType = executorType;</span><br><span class="line">  <span class="keyword">this</span>.exceptionTranslator = exceptionTranslator;</span><br><span class="line">  <span class="keyword">this</span>.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),</span><br><span class="line">      <span class="keyword">new</span> Class[] &#123; SqlSession.class &#125;, <span class="keyword">new</span> SqlSessionInterceptor());</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>被代理的sqlSessionProxy其实也是一个代理，是Java动态代理创建的代理，真实的方法调用可以看<strong>SqlSessionInterceptor</strong>的逻辑：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="class"><span class="keyword">class</span> <span class="title">SqlSessionInterceptor</span> <span class="keyword">implements</span> <span class="title">InvocationHandler</span> </span>&#123;</span><br><span class="line">  <span class="meta">@Override</span></span><br><span class="line">  <span class="function"><span class="keyword">public</span> Object <span class="title">invoke</span><span class="params">(Object proxy, Method method, Object[] args)</span> <span class="keyword">throws</span> Throwable </span>&#123;</span><br><span class="line">    SqlSession sqlSession = getSqlSession(SqlSessionTemplate.<span class="keyword">this</span>.sqlSessionFactory,</span><br><span class="line">        SqlSessionTemplate.<span class="keyword">this</span>.executorType, SqlSessionTemplate.<span class="keyword">this</span>.exceptionTranslator);</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">      Object result = method.invoke(sqlSession, args);</span><br><span class="line">      <span class="keyword">if</span> (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.<span class="keyword">this</span>.sqlSessionFactory)) &#123;</span><br><span class="line">        <span class="comment">// force commit even on non-dirty sessions because some databases require</span></span><br><span class="line">        <span class="comment">// a commit/rollback before calling close()</span></span><br><span class="line">        sqlSession.commit(<span class="keyword">true</span>);</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="keyword">return</span> result;</span><br><span class="line">    &#125; <span class="keyword">catch</span> (Throwable t) &#123;</span><br><span class="line">      Throwable unwrapped = unwrapThrowable(t);</span><br><span class="line">      <span class="keyword">if</span> (SqlSessionTemplate.<span class="keyword">this</span>.exceptionTranslator != <span class="keyword">null</span> &amp;&amp; unwrapped <span class="keyword">instanceof</span> PersistenceException) &#123;</span><br><span class="line">        <span class="comment">// release the connection to avoid a deadlock if the translator is no loaded. See issue #22</span></span><br><span class="line">        closeSqlSession(sqlSession, SqlSessionTemplate.<span class="keyword">this</span>.sqlSessionFactory);</span><br><span class="line">        sqlSession = <span class="keyword">null</span>;</span><br><span class="line">        Throwable translated = SqlSessionTemplate.<span class="keyword">this</span>.exceptionTranslator</span><br><span class="line">            .translateExceptionIfPossible((PersistenceException) unwrapped);</span><br><span class="line">        <span class="keyword">if</span> (translated != <span class="keyword">null</span>) &#123;</span><br><span class="line">          unwrapped = translated;</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;</span><br><span class="line">      <span class="keyword">throw</span> unwrapped;</span><br><span class="line">    &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">      <span class="keyword">if</span> (sqlSession != <span class="keyword">null</span>) &#123;</span><br><span class="line">        closeSqlSession(sqlSession, SqlSessionTemplate.<span class="keyword">this</span>.sqlSessionFactory);</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>单从这段代码上看，可以看到在调用<strong>SqlSessionTemplate</strong>的增删改查时，会获取一个<strong>DefaultSqlSession</strong>，执行完操作后会尝试关闭这个SqlSession。当然，事实上没有这么简单，如果开启了事务，这里<strong>SqlSessionUtils#getSqlSession</strong>会尝试先从当前线程的<strong>ThreadLocal</strong>获取一个<strong>DefaultSqlSession</strong>，如果没有再使用<strong>SqlSessionFactory</strong>创建。而在开启事务的场景下<strong>SqlSessionInterceptor#closeSqlSession</strong>并不会直接关闭这个<strong>SqlSession</strong>，而是在<strong>SqlSessionUtils.SqlSessionSynchronization#afterCompletion</strong>中关闭。<strong>SqlSessionUtils.TransactionSynchronization</strong>实际上是<strong>org.springframework.transaction.support.TransactionSynchronization</strong>的子类，通过实现<strong>TransactionSynchronization</strong>的一些方法可以监听到Spring事务的比如挂起、提交前后等过程。注册<strong>SqlSessionUtils.TransactionSynchronization</strong>的过程在<strong>SqlSessionUtils#registerSessionHolder</strong>可以查看<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">registerSessionHolder</span><span class="params">(SqlSessionFactory sessionFactory, ExecutorType executorType,</span></span></span><br><span class="line"><span class="function"><span class="params">    PersistenceExceptionTranslator exceptionTranslator, SqlSession session)</span> </span>&#123;</span><br><span class="line">  SqlSessionHolder holder;</span><br><span class="line">  <span class="keyword">if</span> (TransactionSynchronizationManager.isSynchronizationActive()) &#123;</span><br><span class="line">    Environment environment = sessionFactory.getConfiguration().getEnvironment();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (environment.getTransactionFactory() <span class="keyword">instanceof</span> SpringManagedTransactionFactory) &#123;</span><br><span class="line">      LOGGER.debug(() -&gt; <span class="string">"Registering transaction synchronization for SqlSession ["</span> + session + <span class="string">"]"</span>);</span><br><span class="line"></span><br><span class="line">      holder = <span class="keyword">new</span> SqlSessionHolder(session, executorType, exceptionTranslator);</span><br><span class="line">      TransactionSynchronizationManager.bindResource(sessionFactory, holder);</span><br><span class="line">      TransactionSynchronizationManager</span><br><span class="line">          .registerSynchronization(<span class="keyword">new</span> SqlSessionSynchronization(holder, sessionFactory));</span><br><span class="line">      holder.setSynchronizedWithTransaction(<span class="keyword">true</span>);</span><br><span class="line">      holder.requested();</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      <span class="keyword">if</span> (TransactionSynchronizationManager.getResource(environment.getDataSource()) == <span class="keyword">null</span>) &#123;</span><br><span class="line">        LOGGER.debug(() -&gt; <span class="string">"SqlSession ["</span> + session</span><br><span class="line">            + <span class="string">"] was not registered for synchronization because DataSource is not transactional"</span>);</span><br><span class="line">      &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> TransientDataAccessResourceException(</span><br><span class="line">            <span class="string">"SqlSessionFactory must be using a SpringManagedTransactionFactory in order to use Spring transaction synchronization"</span>);</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    LOGGER.debug(() -&gt; <span class="string">"SqlSession ["</span> + session</span><br><span class="line">        + <span class="string">"] was not registered for synchronization because synchronization is not active"</span>);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>通过<strong>TransactionSynchronizationManager#registerSynchronization</strong>实现注册，其实也是注册到线程本地的ThreadLocal中。下面简单总结一下SqlSession生命周期的几个要点：</p>
<ol>
<li>非事务场景下，每次数据库操作（增删改查）都会新建一个SqlSession，完成操作后立即关闭SqlSession。</li>
<li>在同一个事务中所有操作共用一个SqlSession，由<strong>TransactionSynchronizationManager</strong>管理。</li>
</ol>
<h3 id="多数据源切换"><a href="#多数据源切换" class="headerlink" title="多数据源切换"></a>多数据源切换</h3><p>&emsp;&emsp;多数据源切换有多种方式，一种是通过创建多个数据源，并且给每个数据源创建不同的<strong>SqlSessionFactory</strong>和<strong>SqlSessionTemplate</strong>，并绑定到不同的Mapper上，具体Demo可以参照<a href="http://blog.didispace.com/spring-boot-learning-21-3-9/" target="_blank" rel="noopener">Spring Boot 2.x基础教程：MyBatis的多数据源配置</a>。还有一种方式是使用<strong>AbstractRoutingDataSource</strong>实现动态路由的多数据源切换（多用于读写分离），不过这种方式其实和MyBatis已经没有什么关系了，是Spring提供的一些功能，不仅仅针对MyBatis。两种方式各有优劣，适用的场景也不大一样，不过相比起来，我更想借机分析一下<strong>AbstractRoutingDataSource</strong>的原理，这种方式在我之前的项目中也已经使用过。<br>&emsp;&emsp;首先<strong>AbstractRoutingDataSource</strong>是一个抽象类（abstract class），实现于<strong>javax.sql.DataSource</strong>，相当于<strong>DataSource</strong>的代理，通过实现<strong>AbstractRoutingDataSource#determineCurrentLookupKey</strong>可以实现动态切换数据源的功能：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">abstract</span> Object <span class="title">determineCurrentLookupKey</span><span class="params">()</span></span>;</span><br></pre></td></tr></table></figure></p>
<p>关于<strong>AbstractRoutingDataSource</strong>实现动态数据源切换的例子网上有很多，直接摘抄一个简单的片段来说明一下：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">DynamicDataSource</span> <span class="keyword">extends</span> <span class="title">AbstractRoutingDataSource</span> </span>&#123;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span>  DynamicDataSource <span class="title">build</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> DynamicDataSource();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">protected</span> Object <span class="title">determineCurrentLookupKey</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> DynamicDataSourceContextHolder.getDataSourceType();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">DataSourceConfiguration</span> </span>&#123;</span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="meta">@ConfigurationProperties</span>(<span class="string">"spring.datasource.druid.master"</span>)</span><br><span class="line">    <span class="function"><span class="keyword">public</span> DataSource <span class="title">masterDataSource</span><span class="params">(DataSourceProperties dataSourceProperties)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> dataSourceProperties.setDataSource(DruidDataSourceBuilder.create().build());</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="meta">@ConditionalOnProperty</span>( prefix = <span class="string">"spring.datasource.druid.slave"</span>, name = <span class="string">"enable"</span>, havingValue = <span class="string">"true"</span>)</span><br><span class="line">    <span class="meta">@ConfigurationProperties</span>(<span class="string">"spring.datasource.druid.slave"</span>)</span><br><span class="line">    <span class="function"><span class="keyword">public</span> DataSource <span class="title">slaveDataSource</span><span class="params">(DataSourceProperties dataSourceProperties)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> dataSourceProperties.setDataSource(DruidDataSourceBuilder.create().build());</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="meta">@Bean</span>(name = <span class="string">"dynamicDataSource"</span>)</span><br><span class="line">    <span class="meta">@Primary</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> DynamicDataSource <span class="title">dynamicDataSource</span><span class="params">(DataSource masterDataSource, DataSource slaveDataSource)</span> </span>&#123;</span><br><span class="line">        Map&lt;Object, Object&gt; targetDataSources = <span class="keyword">new</span> HashMap&lt;&gt;();</span><br><span class="line">        DynamicDataSource dynamicDataSource = DynamicDataSource.build();</span><br><span class="line">        targetDataSources.put(DataSourcesType.MASTER.name(), masterDataSource);</span><br><span class="line">        targetDataSources.put(DataSourcesType.SLAVE.name(), slaveDataSource);</span><br><span class="line">        <span class="comment">//默认数据源配置 DefaultTargetDataSource</span></span><br><span class="line">        dynamicDataSource.setDefaultTargetDataSource(masterDataSource);</span><br><span class="line">        <span class="comment">//额外数据源配置 TargetDataSources</span></span><br><span class="line">        dynamicDataSource.setTargetDataSources(targetDataSources);</span><br><span class="line">        dynamicDataSource.afterPropertiesSet();</span><br><span class="line">        <span class="keyword">return</span> dynamicDataSource;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">DynamicDataSourceContextHolder</span> </span>&#123;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> ThreadLocal&lt;String&gt; contextHolder = <span class="keyword">new</span> ThreadLocal&lt;&gt;();</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">setDataSourceType</span><span class="params">(String dataSourceType)</span> </span>&#123;</span><br><span class="line">        contextHolder.set(dataSourceType);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> String <span class="title">getDataSourceType</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> contextHolder.get();</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">removeDataSourceType</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        contextHolder.remove();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">enum</span>  DataSourcesType &#123;</span><br><span class="line">    MASTER,</span><br><span class="line">    SLAVE</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>以上通过<strong>DynamicDataSourceContextHolder#setDataSourceType</strong>来切换数据源，继承于<strong>AbstractRoutingDataSource</strong>的<strong>DynamicDataSource</strong>通过<strong>DynamicDataSourceContextHolder</strong>的ThreadLocal来获取当前数据源的key，在上面的例子中将我们将<strong>DynamicDataSource</strong>设置为<strong>Primary DataSource</strong>，其实是一种代理的思路，<strong>DynamicDataSource</strong>代理了上面的slaveDataSource和masterDataSource，当获取jdbc的Connection时使用<strong>AbstractRoutingDataSource#getConnection</strong>：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> Connection <span class="title">getConnection</span><span class="params">()</span> <span class="keyword">throws</span> SQLException </span>&#123;</span><br><span class="line">	<span class="keyword">return</span> determineTargetDataSource().getConnection();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> Connection <span class="title">getConnection</span><span class="params">(String username, String password)</span> <span class="keyword">throws</span> SQLException </span>&#123;</span><br><span class="line">	<span class="keyword">return</span> determineTargetDataSource().getConnection(username, password);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p><strong>AbstractRoutingDataSource#determineTargetDataSource</strong>代码如下：<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">protected</span> DataSource <span class="title">determineTargetDataSource</span><span class="params">()</span> </span>&#123;</span><br><span class="line">	Assert.notNull(<span class="keyword">this</span>.resolvedDataSources, <span class="string">"DataSource router not initialized"</span>);</span><br><span class="line">	Object lookupKey = determineCurrentLookupKey();</span><br><span class="line">	DataSource dataSource = <span class="keyword">this</span>.resolvedDataSources.get(lookupKey);</span><br><span class="line">	<span class="keyword">if</span> (dataSource == <span class="keyword">null</span> &amp;&amp; (<span class="keyword">this</span>.lenientFallback || lookupKey == <span class="keyword">null</span>)) &#123;</span><br><span class="line">		dataSource = <span class="keyword">this</span>.resolvedDefaultDataSource;</span><br><span class="line">	&#125;</span><br><span class="line">	<span class="keyword">if</span> (dataSource == <span class="keyword">null</span>) &#123;</span><br><span class="line">		<span class="keyword">throw</span> <span class="keyword">new</span> IllegalStateException(<span class="string">"Cannot determine target DataSource for lookup key ["</span> + lookupKey + <span class="string">"]"</span>);</span><br><span class="line">	&#125;</span><br><span class="line">	<span class="keyword">return</span> dataSource;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>通过<strong>AbstractRoutingDataSource#determineCurrentLookupKey</strong>获取到的key来决定当前的真实数据源，比如本例中到底是slaveDataSource还是masterDataSource。</p>
<h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>&emsp;&emsp;以上通过分析在Spring中如何注入MyBatis Mapper以及管理SqlSession来了解mybatis-spring-boot-starter的一些细节，再结合Spring常用的多数据源管理方式<strong>AbstractRoutingDataSource</strong>，让我对MyBatis以及JDBC的操作更加熟稔于心。</p>

      
    </div>
    
    
    

    

    

    

    <footer class="post-footer">
      
        <div class="post-tags">
          
            <a href="/tags/mybatis/" rel="tag"># mybatis</a>
          
        </div>
      

      
      
      

      
        <div class="post-nav">
          <div class="post-nav-next post-nav-item">
            
              <a href="/2020/12/22/从mybatis到mybatis-spring-boot-starter/" rel="next" title="从mybatis到mybatis-spring-boot-starter">
                <i class="fa fa-chevron-left"></i> 从mybatis到mybatis-spring-boot-starter
              </a>
            
          </div>

          <span class="post-nav-divider"></span>

          <div class="post-nav-prev post-nav-item">
            
              <a href="/2021/01/17/DDD实践笔记/" rel="prev" title="DDD实践笔记">
                DDD实践笔记 <i class="fa fa-chevron-right"></i>
              </a>
            
          </div>
        </div>
      

      
      
    </footer>
  </div>
  
  
  
  </article>



    <div class="post-spread">
      
    </div>
  </div>


          </div>
          


          

  
    <div class="comments" id="comments">
      <div id="disqus_thread">
        <noscript>
          Please enable JavaScript to view the
          <a href="https://disqus.com/?ref_noscript">comments powered by Disqus.</a>
        </noscript>
      </div>
    </div>

  



        </div>
        
          
  
  <div class="sidebar-toggle">
    <div class="sidebar-toggle-line-wrap">
      <span class="sidebar-toggle-line sidebar-toggle-line-first"></span>
      <span class="sidebar-toggle-line sidebar-toggle-line-middle"></span>
      <span class="sidebar-toggle-line sidebar-toggle-line-last"></span>
    </div>
  </div>

  <aside id="sidebar" class="sidebar">
    
    <div class="sidebar-inner">

      

      
        <ul class="sidebar-nav motion-element">
          <li class="sidebar-nav-toc sidebar-nav-active" data-target="post-toc-wrap">
            文章目录
          </li>
          <li class="sidebar-nav-overview" data-target="site-overview-wrap">
            站点概览
          </li>
        </ul>
      

      <section class="site-overview-wrap sidebar-panel">
        <div class="site-overview">
          <div class="site-author motion-element" itemprop="author" itemscope itemtype="http://schema.org/Person">
            
              <p class="site-author-name" itemprop="name">matrix</p>
              <p class="site-description motion-element" itemprop="description"></p>
          </div>

          <nav class="site-state motion-element">

            
              <div class="site-state-item site-state-posts">
              
                <a href="/archives">
              
                  <span class="site-state-item-count">47</span>
                  <span class="site-state-item-name">日志</span>
                </a>
              </div>
            

            
              
              
              <div class="site-state-item site-state-categories">
                <a href="/categories/index.html">
                  <span class="site-state-item-count">18</span>
                  <span class="site-state-item-name">分类</span>
                </a>
              </div>
            

            
              
              
              <div class="site-state-item site-state-tags">
                
                  <span class="site-state-item-count">19</span>
                  <span class="site-state-item-name">标签</span>
                
              </div>
            

          </nav>

          

          

          
          

          
          

          

        </div>
      </section>

      
      <!--noindex-->
        <section class="post-toc-wrap motion-element sidebar-panel sidebar-panel-active">
          <div class="post-toc">

            
              
            

            
              <div class="post-toc-content"><ol class="nav"><li class="nav-item nav-level-3"><a class="nav-link" href="#Mapper注入"><span class="nav-number">1.</span> <span class="nav-text">Mapper注入</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#SqlSession管理"><span class="nav-number">2.</span> <span class="nav-text">SqlSession管理</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#多数据源切换"><span class="nav-number">3.</span> <span class="nav-text">多数据源切换</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#总结"><span class="nav-number">4.</span> <span class="nav-text">总结</span></a></li></ol></div>
            

          </div>
        </section>
      <!--/noindex-->
      

      

    </div>
  </aside>


        
      </div>
    </main>

    <footer id="footer" class="footer">
      <div class="footer-inner">
        <div class="copyright">&copy; <span itemprop="copyrightYear">2021</span>
  <span class="with-love">
    <i class="fa fa-user"></i>
  </span>
  <span class="author" itemprop="copyrightHolder">matrix</span>

  
</div>


  <div class="powered-by">由 <a class="theme-link" target="_blank" href="https://hexo.io">Hexo</a> 强力驱动</div>



  <span class="post-meta-divider">|</span>



  <div class="theme-info">主题 &mdash; <a class="theme-link" target="_blank" href="https://github.com/iissnan/hexo-theme-next">NexT.Pisces</a> v5.1.4</div>
<div class=BbeiAn-info">
  浙ICP备 -
  <a href="http://www.beian.miit.gov.cn">18040498号</a>
  </a>
</div>




        







        
      </div>
    </footer>

    
      <div class="back-to-top">
        <i class="fa fa-arrow-up"></i>
        
      </div>
    

    

  </div>

  

<script type="text/javascript">
  if (Object.prototype.toString.call(window.Promise) !== '[object Function]') {
    window.Promise = null;
  }
</script>









  












  
  
    <script type="text/javascript" src="/lib/jquery/index.js?v=2.1.3"></script>
  

  
  
    <script type="text/javascript" src="/lib/fastclick/lib/fastclick.min.js?v=1.0.6"></script>
  

  
  
    <script type="text/javascript" src="/lib/jquery_lazyload/jquery.lazyload.js?v=1.9.7"></script>
  

  
  
    <script type="text/javascript" src="/lib/velocity/velocity.min.js?v=1.2.1"></script>
  

  
  
    <script type="text/javascript" src="/lib/velocity/velocity.ui.min.js?v=1.2.1"></script>
  

  
  
    <script type="text/javascript" src="/lib/fancybox/source/jquery.fancybox.pack.js?v=2.1.5"></script>
  


  


  <script type="text/javascript" src="/js/src/utils.js?v=5.1.4"></script>

  <script type="text/javascript" src="/js/src/motion.js?v=5.1.4"></script>



  
  


  <script type="text/javascript" src="/js/src/affix.js?v=5.1.4"></script>

  <script type="text/javascript" src="/js/src/schemes/pisces.js?v=5.1.4"></script>



  
  <script type="text/javascript" src="/js/src/scrollspy.js?v=5.1.4"></script>
<script type="text/javascript" src="/js/src/post-details.js?v=5.1.4"></script>



  


  <script type="text/javascript" src="/js/src/bootstrap.js?v=5.1.4"></script>



  


  

    
      <script id="dsq-count-scr" src="https://luyun.disqus.com/count.js" async></script>
    

    
      <script type="text/javascript">
        var disqus_config = function () {
          this.page.url = 'http://yoursite.com/2020/12/27/从mybatis到mybatis-spring-boot-starter（二）/';
          this.page.identifier = '2020/12/27/从mybatis到mybatis-spring-boot-starter（二）/';
          this.page.title = '从mybatis到mybatis-spring-boot-starter（二）';
        };
        var d = document, s = d.createElement('script');
        s.src = 'https://luyun.disqus.com/embed.js';
        s.setAttribute('data-timestamp', '' + +new Date());
        (d.head || d.body).appendChild(s);
      </script>
    

  




	





  














  





  

  

  

  
  

  

  

  

</body>
</html>
